package org.example.filter;


import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.example.common.*;
import org.example.pojo.entity.SysUser;
import org.example.pojo.req.SysUserReq;
import org.example.service.SysUserService;
import org.example.utils.JWTUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 登录提交方式校验
 */
public class LoginUserNamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Setter
    private RedisTemplate redisTemplate;


    public LoginUserNamePasswordAuthenticationFilter(String defaultFilterProcessesUrl, RedisTemplate redisTemplate) {
        super(defaultFilterProcessesUrl);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        //判断请求方法必须是post提交，且提交的数据的内容必须是application/json格式的数据
        if (!request.getMethod().equals("POST") ||
                !(request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE))) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        //获取请求参数
        //获取reqeust请求对象的发送过来的数据流
        ServletInputStream in = request.getInputStream();
        //将数据流中的数据反序列化成Map
        SysUserReq vo = new ObjectMapper().readValue(in, SysUserReq.class);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("utf-8");
        //1.判断参数是否合法
        if (vo == null || StringUtils.isBlank(vo.getUserName())
                || StringUtils.isBlank(vo.getPassword())
                || StringUtils.isBlank(vo.getSessionId()) || StringUtils.isBlank(vo.getCode())) {
            R<Object> resp = R.error(StatusCode.USER_OR_PASSWORD_ERROR);
            response.getWriter().write(new ObjectMapper().writeValueAsString(resp));
            return null;
        }
        //从程序执行的效率看，先进行校验码的校验，成本较低
        String rCheckCode = (String) redisTemplate.opsForValue().get(Constants.CHECK_CODE + vo.getSessionId());
        if (rCheckCode == null || !rCheckCode.equalsIgnoreCase(vo.getCode())) {
            //响应验证码输入错误
            R<Object> resp = R.error(StatusCode.CHECK_CODE_ERROR);
            response.getWriter().write(new ObjectMapper().writeValueAsString(resp));
            return null;
        }
        //检验用户名是否存在
        SysUser user = sysUserService.getByUser(vo.getUserName());
        if (user==null){
            R<Object> resp = R.error(StatusCode.USER_NOT_EXIST);
            response.getWriter().write(new ObjectMapper().writeValueAsString(resp));
            return null;
        }
        if (!passwordEncoder.matches(vo.getPassword(),user.getPassword())){
            R<Object> resp = R.error(StatusCode.PASSWORD_ERROR);
            response.getWriter().write(new ObjectMapper().writeValueAsString(resp));
            return null;
        }
        String username = vo.getUserName();
        username = username.trim();
        String password = vo.getPassword();
        //将用户名和密码信息封装到认证票据对象下
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        //调用认证管理器认证指定的票据对象
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    /**
     * 认证成功处理方法
     *
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        //获取用户的详情信息
        LoginUserDetail userDetail = (LoginUserDetail) authResult.getPrincipal();
        //组装LoginRespVoExt
        //获取用户名称
        String username = userDetail.getUsername();
        //获取权限集合对象
        List<GrantedAuthority> authorities = new ArrayList<>(userDetail.getAuthorities());
        String auStrList = authorities.toString();
        //复制userDetail属性值到LoginRespVoExt对象即可
        LoginRespVoExt resp = new LoginRespVoExt(userDetail.getUsername(), "", userDetail.getAuthorities());
        BeanUtils.copyProperties(userDetail, resp);
        //生成token字符串:将用户名称和权限信息价格生成token字符串
        String tokenStr = JWTUtils.createToken(userDetail.getUserId(),username, auStrList);
        resp.setToken(tokenStr);
        //封装统一响应结果
        R<Object> r = R.ok(resp);
        String respStr = new ObjectMapper().writeValueAsString(r);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(respStr);
    }

    /**
     * 认证失败处理方法
     *
     * @param request
     * @param response
     * @param failed
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request,
                                              HttpServletResponse response,
                                              AuthenticationException failed) throws IOException, ServletException {
        R<Object> r = R.error(StatusCode.SYSTEM_ERROR);
        String respStr = new ObjectMapper().writeValueAsString(r);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(respStr);
    }
}
